/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010.

	This file is part of Maxsi Engine.

	Maxsi Engine is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi engine is distributed in the hope that it will be useful, but WITHOUT
	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
	FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
	for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	MaxsiEngine.dll
	A general purpose C++ library for the Maxsi Technology Project.

	MaxsiEngineFile.cpp
	Libary for import and export of Maxsi Engine Files (.mef).

******************************************************************************/

#include "MaxsiEngine.h"

#if 0

#ifdef WIN32
#include <io.h>
#include <direct.h>
#include <fcntl.h>
#include <SYS\Stat.h>
#endif

BeginMaxsiNamespace

#define SetStatus(SoFar,Total) if ( ProgressFunc ) { ProgressFunc((int)(SoFar*100.0/Total)); }

BYTE* GetVariableData(List list, MESTR* VariableName)
{
	MaxsiVariable* Var = GetVariable(list, VariableName);

	if (Var)
	{
		BYTE* Output = new BYTE[Var->DataSize];

		if ( Output == NULL ) { return NULL; }	

		memcpy(Output, Var->VariableData+Var->NameSize, Var->DataSize);

		return Output;
	}

	return NULL;
}

MaxsiVariable* GetVariable(List list, MESTR* VariableName)
{
	if ( list )
	{
		size_t			NumVariables	=	GetListSize(list);
		MaxsiVariable*	Variable		=	0;

		for ( size_t N = 0; N < NumVariables; N++ )
		{
			Variable = (MaxsiVariable*)GetListEntry(list, N);

			MAXSI_REMOVE_OLD_CODE();		

			//UTF8_t*	TheVariableName	= new UTF8_t[Variable->NameSize+1];

			//if ( TheVariableName == NULL ) { return NULL; }

			//memcpy(TheVariableName, Variable->VariableData, Variable->NameSize);
			//TheVariableName[Variable->NameSize] = 0;

			if ( MESTRCMP(VariableName, Variable->Name) == 0)
			{
				delete[] TheVariableName;
				return Variable;
			}

			delete[] TheVariableName;
		}
	}
	return NULL;
}

MaxsiVariable* SetVariable(List list, MESTR* VariableName, BYTE* Buffer, size_t BufferSize)
{
	if ( BufferSize > INT32_MAX ) { return MAXSI_ERROR_INPUT_TOO_BIG; }

	UTF8_t*	VariableNameUTF8	=	MESTR2UTF8(VariableName);

	if ( VariableNameUTF8 == NULL ) { return NULL; }

	size_t	VariableNameSize	=	strlen((char*)VariableNameUTF8);

	MaxsiVariable*	Var			=	NULL;

	if ( VariableNameSize >= 255 ) { delete[] VariableNameUTF8; return NULL; } // Longer strings are not supported.

	if ( list != NULL )
	{
		Var		=	GetVariable(list, VariableName);

		if ( Var == NULL  )
		{
			MaxsiVariable StackVar;		

			StackVar->VariableData = NULL;

			// Create a copy of StackVar in our list and check if the memory allocation failed.
			if ( AddListEntry(&list, (BYTE*)Var, sizeof(MaxsiVariable)) == false )
			{
				delete[] VariableNameUTF;
				return NULL;								
			}

			// Retrieve a pointer to our newest list member!
			Var		=	GetListEntry(list, GetListSize(list)-1);
		}

		if ( Var != NULL )
		{
			if ( Var->VariableData ) { delete[] Var->VariableData; }

			Var->NameLength	=	(UTF8_t)VariableNameSize;
			Var->DataSize	=	BufferSize;
			Var->Data		=	new BYTE[BufferSize];
			
			if ( Var->Data == NULL ) { delete[] VariableNameUTF8; return NULL; }

			Var->Name		=	BuildString(1, VariableName);
			
			if ( Var->Name == NULL ) { delete[] VariableNameUTF8; delete[] Var->Data; return NULL; }

			// Copy the buffer to the new buffer.
			memcpy(Var->Data, Buffer, BufferSize);
		}
	}

	delete[] VariableNameUTF8;

	return Var;
}

List DuplicateVariableList(List list)
{
	size_t			EntrySize	=	0;
	BYTE*			NewBuffer	=	NULL;
	MaxsiVariable*	TheVariable	=	NULL;
	size_t			ListSize	=	GetListSize(list);
	List			NewList		=	CreateList(ListSize);
	MaxsiVariable	StackVar;

	if ( ListSize )
	{
		for (size_t N = 0; N < ListSize; N++)
		{
			TheVariable		=	(MaxsiVariable*)GetListEntry(list, N);

			if ( TheVariable != NULL )
			{			
				memcpy(&StackVar, TheVariable, sizeof(StackVar));

				// Allocate a copy of the original buffer.
				StackVar.Data	=	new BYTE[TheVariable->DataSize];

				// Check for error conditions.
				if ( StackVar.Data == NULL ) { DeleteVariableList(NewList); return NULL }

				// Create a copy of the variable name.
				StackVar.Name	=	BuildString(TheVariable->Name);

				// Check for error conditions.
				if ( StackVar.Name == NULL ) { DeleteVariableList(NewList); delete[] StackVar.Data; return NULL }

				// Set the list entry and check for error conditions.
				if ( SetListEntry(NewList, N, (BYTE*)&StackVar, sizeof(StackVar)) == false )
				{
					DeleteVariableList(NewList);
					delete[] StackVar.Data;
					delete[] StackVar.Name;
					return NULL;
				}	
			}
		}
	}

	return NewList;
}

bool DeleteVariableList(List list)
{
	if ( list == NULL ) { return false; }

	MaxsiVariable*	TheVariable	=	NULL;
	size_t			ListSize	=	GetListSize(list);

	if ( ListSize )
	{
		for (size_t N = 0; N < ListSize; N++)
		{
			TheVariable		=	(MaxsiVariable*)GetListEntry(list,N);

			if ( TheVariable && TheVariable->Data ) { delete[] TheVariable->Data; }
			if ( TheVariable && TheVariable->Name ) { delete[] TheVariable->Name; }
		}
	}

	return DeleteList(list);
}

//=============================================================================
// MaxsiEngineFileFragment
//=============================================================================

// Initalize the fragment
MaxsiEngineFileFragment::MaxsiEngineFileFragment( void )
{
	FileContent		=	NULL;
	FileName		=	NULL;
	FileSize		=	0;
	Fragment		=	NULL;
	Variables		=	NULL;
}

// Terminate the fragment
MaxsiEngineFileFragment::~MaxsiEngineFileFragment( void )
{
	DeleteVariableList(Variables);

	if ( FileContent ) { delete[] FileContent; }
	if ( FileName ) { delete[] FileName; }
	if ( Fragment ) { delete Fragment; }
}

// Get the filename
MESTR* MaxsiEngineFileFragment::GetFileName( void )
{
	return FileName;
}

FileSize_t MaxsiEngineFileFragment::GetFileSize( void )
{
	return FileSize;
}

// Return some pointers and such to the information
BYTE* MaxsiEngineFileFragment::Read(size_t* BufferSize)
{
	if ( BufferSize )
	{
		*BufferSize = GetFileSize();
	}

	return FileContent;
}

// Loads the file into memory from .MEF format
MaxsiError MaxsiEngineFileFragment::Write(BYTE* Data, BYTE** CallerPos)
{
	if ( FileContent ) { delete[] FileContent; }
	if ( FileName ) { delete[] FileName; }

	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	BYTE* Pos	=	Data;

	uint8_t FileNameSize = 0;
	memcpy(&FileNameSize, Pos, sizeof(FileNameSize)); Pos+=sizeof(FileNameSize);

	UTF8_t*	FileNameUTF8	=	new UTF8_t[(size_t)FileNameSize + 1];

	if ( FileNameUTF == NULL ) { Result = MAXSI_ERROR_MEM_ALLOC_FAIL; }

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		memcpy(FileNameUTF8, Pos, FileNameSize);

		FileNameUTF[FileNameSize]	=	0;

		// Convert the ascii filename to the local MESTR type.
		FileName			=	UTF82MESTR(FileNameA);

		delete[] FileNameUTF;
		
		if ( FileName == NULL ) { Result = MAXSI_ERROR_MEM_ALLOC_FAIL; }
	}

	Pos	+=	FileNameSize;

	// Read the filesize.
	memcpy(&FileSize, Pos, sizeof(FileSize)); Pos+=sizeof(FileSize);

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		// Attempt to allocate a big enough in-memory buffer.
		FileContent		=	new BYTE[(size_t)FileSize+1];

		if ( FileContent != NULL )
		{
			memcpy(FileContent, Pos, (size_t)FileSize+1);
		}
	}

	MAXSI_KNOWN_BUG("There shouldn't be +1 here, it is only left for back support.");

#ifndef Maxsi_Disable_Bugs
	Pos	+=	FileSize+1; // TODO: Why the hell +1?
#else
	Pos	+=	FileSize;
#endif

	if ( CallerPos )
	{
		*CallerPos = Pos;
	}

	return Result;
}

// Loads the file into memory from a buffer.
MaxsiError MaxsiEngineFileFragment::Write(BYTE* Data, size_t DataLen, MESTR* Path, BYTE** CallerPos)
{
	if (Path)
	{
		if ( FileName ) { delete[] FileName; }
		
		FileName	=	BuildString(1, Path);

		if ( FileName == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }
	}

	if ( FileName )
	{
		delete[] FileContent;
		FileContent = new BYTE[(size_t)DataLen];

		if ( FileContent == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

		MAXSI_KNOWN_BUG("There shouldn't be -1 here, it is only left for back support.");

#ifndef Maxsi_Disable_Bugs
		FileSize = DataLen-1; // TODO: What the hell is going on here, it should just be equal.

		if ( DataLen == 0 ) { FileSize = 0; } // Empty files! :D
#else
		FileSize = DataLen;
#endif

		memcpy(FileContent,Data,(size_t)DataLen);

		if ( CallerPos ) { *CallerPos = Data; }
	}
	else
	{
		// We cannot write without a filename
		return MAXSI_ERROR_INVALID_INPUT;
	}

	return MAXSI_ERROR_SUCCESS;
}

//=============================================================================
// MaxsiEngineFile
//=============================================================================

// Initalize the File
MaxsiEngineFile::MaxsiEngineFile( void )
{
	FilePos		=	NULL;
	Fragment	=	NULL;
	NumFiles	=	0;
	Version		=	2;
	Variables	=	NULL;
}


// Terminate the File
MaxsiEngineFile::~MaxsiEngineFile( void )
{
	DeleteVariableList( Variables );
	if ( FilePos ) { delete FilePos; }
	if ( Fragment ) { delete Fragment; }
}

// Initalize the File and load it
MaxsiEngineFile::MaxsiEngineFile(MESTR* Path)
{
	MaxsiEngineFile();

	ImportFile(Path);
}

// Imports a pure .MEF formated file into memory and passes it on to the proper function
MaxsiError MaxsiEngineFile::ImportFile(MESTR* Path, ProgressFunc_Type ProgressFunc)
{
	MaxsiHandle		FileHandle	=	FileSystem()->OpenFile(Path, MAXSI_FILE_READ);

	if ( FileHandle == NULL ) 
	{
		return MAXSI_ERROR_COULD_NOT_OPEN; // Couldn't open
	}
	else
	{
		FileSize_t FileSize = FileSystem()->GetFileSize(FileHandle);

		if ( FileSize > SIZE_MAX ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

		MAXSI_KNOWN_BUG("There shouldn't be +1 here, it is only left for back support.");

#ifndef Maxsi_Disable_Bugs
		BYTE* AllocatedBuffer	=	new BYTE[(size_t)FileSize+1];
#else
		BYTE* AllocatedBuffer	=	new BYTE[(size_t)FileSize];
#endif

		if ( AllocatedBuffer == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

		FileSize_t	FileLeft	=	FileSize;

		while ( FileLeft )
		{
			SetStatus((FileSize-FileLeft),FileSize);

			if ( FileLeft > 1024*1024)
			{
				FileSystem()->ReadFile(FileHandle, AllocatedBuffer+(FileSize-FileLeft), 1024*1024);
				FileLeft-=1024*1024;
			}
			else
			{
				FileSystem()->ReadFile(FileHandle, AllocatedBuffer+(FileSize-FileLeft), FileLeft);
				FileLeft = 0;
			}
		}

		FileSystem()->CloseFile(FileHandle);
		
		MaxsiError Result = ImportFile(AllocatedBuffer, FileSize, ProgressFunc);
		SetStatus(1,1);

		delete[] AllocatedBuffer;

		return Result;
	}
}

// Imports a .MEF formated file into memory, and validates the header in some way
// calling the correct subversion function.
int	MaxsiEngineFile::ImportFile(char* Data, __int64 DataLen, ProgressFunc_Type ProgressFunc)
{
	SetStatus(0,1);
	char* Pos = Data;

	char* Header = new char[3];

	memcpy(Header,Pos,strlen("ME")); Pos+=strlen("ME")+1;

	Header[strlen("ME")] = 0; // Avoid crashes if invalid data.

	if (strcmp(Header,"ME") == 0)
	{
		delete Header;

		unsigned int FileVersion = 0;
		memcpy(&FileVersion,Pos,sizeof(FileVersion)); Pos+=sizeof(FileVersion);

		if ( FileVersion == 1)
		{
			Version = FileVersion;
			return ImportFile_Version_1(Pos,DataLen-sizeof(Header)-sizeof(FileVersion),ProgressFunc);
		}
		else if ( FileVersion == 2)
		{
			Version = FileVersion;
			return ImportFile_Version_2(Pos,DataLen-sizeof(Header)-sizeof(FileVersion),ProgressFunc);
		}
		else
		{
			// Not Supported FileVersion!
			return MAXSI_ERROR_UNSUPPORTED_VERSION;
		}
	}
	else
	{
		delete Header;
		return MAXSI_ERROR_INVALID_HEADER; // Invalid header. This is not a valid .MEF formated file.
	}
}

// Imports the .MEF file as if it was written in the .MEF Version 2 format
// Note: This function does not check if the data is written in a valid format,
// the calling function should itself ensure the data is a valid format.
int	MaxsiEngineFile::ImportFile_Version_1(char* Data, __int64 DataLen, ProgressFunc_Type ProgressFunc)
{
	char* Pos = Data;

	// Hey!
	unsigned __int64 NumFiles = 0;
	memcpy(&NumFiles,Pos,sizeof(NumFiles)); Pos+=sizeof(NumFiles);

	unsigned __int64 I;
	int Result = 0;

	if ( NumFiles < 1 ) // No files in this .MEF file. Stop import successfully
	{
		return MAXSI_ERROR_SUCCESS;
	}

	for (I=0; I < NumFiles; I++)
	{
		SetStatus(((unsigned int)Pos-(unsigned int)Data),DataLen);
		MaxsiEngineFileFragment* TMP = new MaxsiEngineFileFragment;
		Result = TMP->Write(Pos,&Pos);

		if ( Result == MAXSI_ERROR_SUCCESS )
		{
			TMP->Fragment = Fragment;
			Fragment = TMP;
		}
		else
		{
			delete TMP;
			return Result;
		}
	}

	return MAXSI_ERROR_SUCCESS;
}

// Imports the .MEF file as if it was written in the .MEF Version 1 format
// Note: This function does not check if the data is written in a valid format,
// the calling function should itself ensure the data is a valid format.
int	MaxsiEngineFile::ImportFile_Version_2(char* Data, __int64 DataLen, ProgressFunc_Type ProgressFunc)
{
	char* Pos = Data;

	// First read the variables
	unsigned int	NumVariables	= 0;
	MEFFVariable*	Variable		= 0;

	memcpy(&NumVariables,Pos,sizeof(NumVariables)); Pos+=sizeof(NumVariables);
	if ( NumVariables )
	{
		Variables	= CreateList((size_t)NumVariables);
		for	(unsigned int N	= 0; N < NumVariables; N++)
		{
			Variable = new MEFFVariable;		
			memcpy(&(Variable->NameSize),Pos,sizeof(Variable->NameSize)); Pos+=sizeof(Variable->NameSize);
			memcpy(&(Variable->DataSize),Pos,sizeof(Variable->DataSize)); Pos+=sizeof(Variable->DataSize);
			unsigned int VariableData_Size = Variable->DataSize + Variable->NameSize;

			Variable->VariableData = new char[VariableData_Size];
			memcpy(Variable->VariableData,Pos,VariableData_Size); Pos+=VariableData_Size;
			SetListEntry(Variables,N,(BYTE*)Variable,sizeof(MEFFVariable));
			delete Variable;
		}
	}

	unsigned __int64 NumFiles = 0;
	memcpy(&NumFiles,Pos,sizeof(NumFiles)); Pos+=sizeof(NumFiles);

	unsigned __int64 I;
	int Result = 0;

	if ( NumFiles < 1 ) // No files in this .MEF file. Stop importing successfully
	{
		return MAXSI_ERROR_SUCCESS;
	}

	for (I=0; I < NumFiles; I++)
	{
		SetStatus(((unsigned int)Pos-(unsigned int)Data),DataLen);
		MaxsiEngineFileFragment* TMP = new MaxsiEngineFileFragment;

		// Load the per-file variables
		memcpy(&NumVariables,Pos,sizeof(NumVariables)); Pos+=sizeof(NumVariables);
		if ( NumVariables )
		{
			TMP->Variables	= CreateList((size_t)NumVariables);
			for	(unsigned int N	= 0; N < NumVariables; N++)
			{
				Variable = new MEFFVariable;		
				memcpy(&(Variable->NameSize),Pos,sizeof(Variable->NameSize)); Pos+=sizeof(Variable->NameSize);
				memcpy(&(Variable->DataSize),Pos,sizeof(Variable->DataSize)); Pos+=sizeof(Variable->DataSize);
				unsigned int VariableData_Size = Variable->DataSize + Variable->NameSize;

				Variable->VariableData = new char[VariableData_Size];
				memcpy(Variable->VariableData,Pos,VariableData_Size); Pos+=VariableData_Size;
				SetListEntry(TMP->Variables,N,(BYTE*)Variable,sizeof(MEFFVariable));
				delete Variable;
			}
		}

		Result = TMP->Write(Pos,&Pos);

		if ( Result == MAXSI_ERROR_SUCCESS )
		{
			TMP->Fragment = Fragment;
			Fragment = TMP;
		}
		else
		{
			delete TMP;
			return Result;
		}
	}

	return MAXSI_ERROR_SUCCESS;
}


// Opens the path and writes all the content of the MaxsiEngineFile class this is
// in the .MEF format version the Version variable is.
int	MaxsiEngineFile::ExportFile(MESTR* Path, ProgressFunc_Type ProgressFunc)
{
	int Result = 0;

	char*				AllocatedBuffer = 0;
	unsigned __int64	FileSize = 0;
	unsigned __int64	FileLeft = 0;

	Result = ExportFile(&AllocatedBuffer,&FileSize, ProgressFunc);

	remove(Path);

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		HANDLE	FileHandle	=	CreateFileA(Path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

		if (FileHandle  == INVALID_HANDLE_VALUE)
		{
			delete[] AllocatedBuffer;
			return ERROR_COULDNT_OPEN_FILE; // Couldn't open
		}
		else
		{
			FileLeft = FileSize;
			while ( FileLeft )
			{
				DWORD	Written;

				SetStatus((FileSize-FileLeft),FileSize);
				if ( FileLeft > 1048576)
				{
					::WriteFile(FileHandle, AllocatedBuffer+(FileSize-FileLeft), 1048576, &Written, NULL);
					FileLeft-=1048576;
				}
				else
				{
					::WriteFile(FileHandle, AllocatedBuffer+(FileSize-FileLeft), (DWORD)FileLeft, &Written, NULL);
					FileLeft = 0;
				}
			}

			SetEndOfFile(FileHandle);

			CloseHandle(FileHandle);
		}
	}

	delete[] AllocatedBuffer;

	return Result;
}

// Writes the data to a stream in the .MEF in the version the Version Variable is,
// the calling function must deallocate the stream itself, to avoid memory leaks.
int	MaxsiEngineFile::ExportFile(char** Data, unsigned __int64* DataLen, ProgressFunc_Type ProgressFunc)
{
	if ( Version == 1)
	{
		return ExportFile_Version_1(Data,DataLen,ProgressFunc);
	}
	else if ( Version == 2)
	{
		return ExportFile_Version_2(Data,DataLen,ProgressFunc);
	}
	else
	{
		// Not Supported FileVersion!
		return MAXSI_ERROR_UNSUPPORTED_VERSION;
	}
}

// Writes the data to a stream in the .MEF Version 1 format
// the calling function must deallocate the stream itself, to avoid memory leaks.
int	MaxsiEngineFile::ExportFile_Version_1(char** Data, unsigned __int64* DataLen, ProgressFunc_Type ProgressFunc)
{
	if ( ProgressFunc ) { ProgressFunc(0); };
	unsigned __int64 FileSize = 0;

	// GLOBAL HEADER

	// char[2], unsigned int version
	FileSize += strlen("ME") + 1 + sizeof(unsigned int);

	// VERSION-SPECIFIC HEADER

	// unsigned __int64 NumFiles
	FileSize += sizeof(unsigned __int64);

	// FILECONTENT, UNCOMPRESSED
	
	unsigned __int64 NumFiles = 0;

	for (MaxsiEngineFileFragment* TMP = Fragment; TMP; TMP = TMP->Fragment)
	{
		NumFiles++;

		FileSize += sizeof( unsigned char );	// unsigned char FileNameSize
		FileSize += strlen(TMP->GetFileName());	// char[FileNameSize] FileName
		FileSize += sizeof( unsigned __int64 );	// unsigned __int64 FileSize
		FileSize += TMP->GetFileSize() + 2;			// char[FileSize] FileContent
	}

	// Now the amount of data is measured, allocate all the required space.

	unsigned __int64 Offset = 0;
	unsigned int FileVersion = 2;
	char* AllocatedBuffer = new char[(size_t)FileSize];

	// GLOBAL HEADER

	SetStatus(Offset,FileSize);

	memcpy(AllocatedBuffer + Offset,"ME",strlen("ME")); Offset += strlen("ME");
	memset(AllocatedBuffer + Offset,0,1); Offset += 1;
	memcpy(AllocatedBuffer + Offset,&FileVersion,sizeof(FileVersion));  Offset += sizeof(FileVersion);

	SetStatus(Offset,FileSize);

	// VERSION-SPECIFIC HEADER

	memcpy(AllocatedBuffer + Offset,&NumFiles,sizeof(NumFiles));  Offset += sizeof(NumFiles);

	SetStatus(Offset,FileSize);

	unsigned char curFileNameSize = 0;
	unsigned __int64 curFileSize = 0;

	// FILECONTENT, UNCOMPRESSED

	for (MaxsiEngineFileFragment* TMP = Fragment; TMP; TMP = TMP->Fragment)
	{
		SetStatus(Offset,FileSize);
		// FileName
		curFileNameSize = (char)strlen(TMP->GetFileName()) + 1;
		memcpy(AllocatedBuffer + Offset,&curFileNameSize,sizeof(curFileNameSize)); Offset += sizeof(curFileNameSize);
		strcpy_s(AllocatedBuffer + Offset,curFileNameSize,TMP->GetFileName()); Offset += curFileNameSize;

		// FileContent
		curFileSize = TMP->GetFileSize();
		memcpy(AllocatedBuffer + Offset,&curFileSize,sizeof(curFileSize)); Offset += sizeof(curFileSize);
		memcpy(AllocatedBuffer + Offset,TMP->Read(),(size_t)curFileSize+1); Offset += curFileSize+1;
	}
		
	*Data = AllocatedBuffer;
	*DataLen = FileSize;

	if ( FileSize == Offset )
	{
		if ( ProgressFunc ) { ProgressFunc(100); };
		return MAXSI_ERROR_SUCCESS;
	}
	else
	{
		return MAXSI_ERROR_THIS_SHOULD_NOT_HAPPEN;
	}
}

// Writes the data to a stream in the .MEF Version 2 format
// the calling function must deallocate the stream itself, to avoid memory leaks.
int	MaxsiEngineFile::ExportFile_Version_2(char** Data, unsigned __int64* DataLen, ProgressFunc_Type ProgressFunc)
{
	if ( ProgressFunc ) { ProgressFunc(0); };
	unsigned __int64 FileSize = 0;

	// GLOBAL HEADER

	// char[2], unsigned int version
	FileSize += strlen("ME") + 1 + sizeof(unsigned int);

	// VERSION-SPECIFIC HEADER
	
	// VariableArray Variables
	FileSize += sizeof(unsigned int);	// NumVariables
	if (Variables)
	{
		unsigned int	NumVariables = (unsigned int)GetListSize(Variables);
		MEFFVariable*	Variable = 0;
		for (unsigned int N = 0; N < NumVariables; N++)
		{
			Variable = (MEFFVariable*)GetListEntry(Variables,N);
			FileSize += sizeof(unsigned char);	// NameSize
			FileSize += sizeof(unsigned int);	// DataSize
			FileSize += Variable->NameSize
					  + Variable->DataSize;	// char[NameSize+DataSize] VariableData

		}
	}

	// unsigned __int64 NumFiles
	FileSize += sizeof(unsigned __int64);

	// FILECONTENT, UNCOMPRESSED
	
	unsigned __int64 NumFiles = 0;

	for (MaxsiEngineFileFragment* TMP = Fragment; TMP; TMP = TMP->Fragment)
	{
		NumFiles++;

		FileSize += sizeof(unsigned int);	// NumVariables
		if (TMP->Variables)
		{
			unsigned int	NumVariables = (unsigned int)GetListSize(TMP->Variables);
			MEFFVariable*	Variable = 0;
			for (unsigned int N = 0; N < NumVariables; N++)
			{
				Variable = (MEFFVariable*)GetListEntry(TMP->Variables,N);
				FileSize += sizeof(unsigned char);	// NameSize
				FileSize += sizeof(unsigned int);	// DataSize
				FileSize += Variable->NameSize
						  + Variable->DataSize;	// char[NameSize+DataSize] VariableData

			}
		}

		FileSize += sizeof( unsigned char );	// unsigned char FileNameSize
		FileSize += strlen(TMP->GetFileName());	// char[FileNameSize] FileName
		FileSize += sizeof( unsigned __int64 );	// unsigned __int64 FileSize
		FileSize += TMP->GetFileSize() + 2;			// char[FileSize] FileContent
	}

	// Now the amount of data is measured, allocate all the required space.

	unsigned __int64 Offset = 0;
	unsigned int FileVersion = 2;
	char* AllocatedBuffer = new char[(size_t)FileSize];

	// GLOBAL HEADER

	SetStatus(Offset,FileSize);

	memcpy(AllocatedBuffer + Offset,"ME",strlen("ME")); Offset += strlen("ME");
	memset(AllocatedBuffer + Offset,0,1); Offset += 1;
	memcpy(AllocatedBuffer + Offset,&FileVersion,sizeof(FileVersion));  Offset += sizeof(FileVersion);

	SetStatus(Offset,FileSize);

	// VERSION-SPECIFIC HEADER

	
	// Variables
	unsigned int	NumVariables = (unsigned int)GetListSize(Variables);
	memcpy(AllocatedBuffer + Offset,&NumVariables,sizeof(NumVariables));  Offset += sizeof(NumVariables);

	if (Variables && NumVariables)
	{
		MEFFVariable*	Variable = 0;
		for (unsigned int N = 0; N < NumVariables; N++)
		{
			Variable = (MEFFVariable*)GetListEntry(Variables,N);
			memcpy(AllocatedBuffer + Offset,&(Variable->NameSize),sizeof(Variable->NameSize));  Offset += sizeof(Variable->NameSize);
			memcpy(AllocatedBuffer + Offset,&(Variable->DataSize),sizeof(Variable->DataSize));  Offset += sizeof(Variable->DataSize);
			memcpy(AllocatedBuffer + Offset,&(Variable->VariableData),Variable->NameSize+Variable->DataSize);  Offset += Variable->NameSize+Variable->DataSize;
		}
	}

	memcpy(AllocatedBuffer + Offset,&NumFiles,sizeof(NumFiles));  Offset += sizeof(NumFiles);

	SetStatus(Offset,FileSize);

	unsigned char curFileNameSize = 0;
	unsigned __int64 curFileSize = 0;

	// FILECONTENT, UNCOMPRESSED

	for (MaxsiEngineFileFragment* TMP = Fragment; TMP; TMP = TMP->Fragment)
	{
		SetStatus(Offset,FileSize);

		// Variables
		unsigned int	NumVariables = (unsigned int)GetListSize(TMP->Variables);
		memcpy(AllocatedBuffer + Offset,&NumVariables,sizeof(NumVariables));  Offset += sizeof(NumVariables);

		if (TMP->Variables && NumVariables)
		{
			MEFFVariable*	Variable = 0;
			for (unsigned int N = 0; N < NumVariables; N++)
			{
				Variable = (MEFFVariable*)GetListEntry(TMP->Variables,N);
				memcpy(AllocatedBuffer + Offset,&(Variable->NameSize),sizeof(Variable->NameSize));  Offset += sizeof(Variable->NameSize);
				memcpy(AllocatedBuffer + Offset,&(Variable->DataSize),sizeof(Variable->DataSize));  Offset += sizeof(Variable->DataSize);
				memcpy(AllocatedBuffer + Offset,Variable->VariableData,Variable->NameSize+Variable->DataSize);  Offset += Variable->NameSize+Variable->DataSize;
			}
		}

		// FileName
		curFileNameSize = (char)strlen(TMP->GetFileName()) + 1;
		memcpy(AllocatedBuffer + Offset,&curFileNameSize,sizeof(curFileNameSize)); Offset += sizeof(curFileNameSize);
		strcpy_s(AllocatedBuffer + Offset,curFileNameSize,TMP->GetFileName()); Offset += curFileNameSize;

		// FileContent
		curFileSize = TMP->GetFileSize();
		memcpy(AllocatedBuffer + Offset,&curFileSize,sizeof(curFileSize)); Offset += sizeof(curFileSize);
		memcpy(AllocatedBuffer + Offset,TMP->Read(),(size_t)curFileSize+1); Offset += curFileSize+1;
	}
		
	*Data = AllocatedBuffer;
	*DataLen = FileSize;

	if ( FileSize == Offset )
	{
		if ( ProgressFunc ) { ProgressFunc(100); };
		return MAXSI_ERROR_SUCCESS;
	}
	else
	{
		//cerr << "FETAL .MEF EXPORT ERROR: FileSize != Offset,\n	FileSize = " << FileSize << "	" << "Offset = " << Offset << "\nPress Enter to continue";
		//cin.get();
		return MAXSI_ERROR_THIS_SHOULD_NOT_HAPPEN;
	}
}

// Caller provides the path of the desired file and the function returns a pointer to it, if it exists.
MaxsiEngineFileFragment* MaxsiEngineFile::ReadFile(MESTR* Path, List* Variables)
{
	for (MaxsiEngineFileFragment* TMP = Fragment; TMP; TMP = TMP->Fragment)
	{
		if (_stricmp(Path,TMP->GetFileName()) == 0 )
		{
			return TMP;
		}
	}
	return 0;
}

// Creates a new fragment and forwards the call.
MaxsiEngineFileFragment* MaxsiEngineFile::WriteFile(char* Data)
{
	MaxsiEngineFileFragment* TMP = new MaxsiEngineFileFragment;

	if ( TMP->Write(Data) == MAXSI_ERROR_SUCCESS )
	{
		TMP->Fragment = Fragment;
		Fragment = TMP;
		return Fragment;
	}
	else
	{
		delete TMP; // Failure, couldn't write this thing.
		return 0;
	}
}

// Opens a file, imports stuff and adds it with its name as Alias
MaxsiEngineFileFragment* MaxsiEngineFile::WriteFile(char* Path, char* Alias, List Variables)
{	
	HANDLE	FileHandle	=	CreateFileA(Path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (FileHandle  == INVALID_HANDLE_VALUE)
	{
		return NULL; // Couldn't open
	}
	else
	{
		DWORD FileSize = GetFileSize(FileHandle, NULL);

		char* AllocatedBuffer = new char[FileSize+1];

		DWORD	Read;

		::ReadFile(FileHandle, AllocatedBuffer, FileSize, &Read, NULL);
	
		MaxsiEngineFileFragment* Result = WriteFile(Alias,AllocatedBuffer,FileSize,Variables);

		delete[] AllocatedBuffer;

		return Result;
	}
}

// Creates a new fragment and forwards the call.
MaxsiEngineFileFragment* MaxsiEngineFile::WriteFile(MESTR* Path, char* Data, size_t DataLen, List Variables)
{
	if ( Variables )
	{
		Variables = DuplicateVariableList(Variables);
	}
	else
	{
		Variables = CreateList(0);
	}

#ifndef MAXSIEXTRACTOR	// The extractor don't need this

	char*		FileHash = CalculateSHA1((BYTE*)Data,(size_t)DataLen);
	MEFFVariable* _SHA1 = new MEFFVariable;
	_SHA1->NameSize = (unsigned char)strlen("_SHA1");
	_SHA1->DataSize = (unsigned int)strlen(FileHash)+1;	
	_SHA1->VariableData = BuildString(2,"_SHA1",FileHash);
	delete[]	FileHash;

	SetListSize(Variables,GetListSize(Variables)+1);
	SetListEntry(Variables,GetListSize(Variables)-1,(BYTE*)_SHA1,sizeof(MEFFVariable));

#endif

	MaxsiEngineFileFragment* TMP = new MaxsiEngineFileFragment;

#ifndef MAXSIEXTRACTOR	// The extractor shouldn't load this, since zlib1.dll may not exist!
	MEFFVariable*	_Compression			=	GetVariable(Variables,"_Compression");
	MEFFVariable*	_CompressionFormat		=	GetVariable(Variables,"_CompressionFormat");
	char*			CompressedBuffer		=	0;

	if (_Compression && _CompressionFormat) 
	{		
		uLong	Compressed_Size	= 0;
		memcpy(&Compressed_Size,_Compression->VariableData+_Compression->NameSize,sizeof(Compressed_Size));

		if ( Compressed_Size == 0)// If not compressed yet, compress now!
		{
			if (_stricmp(_CompressionFormat->VariableData+_CompressionFormat->NameSize,"zlib")==0)
			{
				uLong	CompressedBuffer_Len	=	sizeof(z_stream_s)+compressBound((uLong)DataLen);
						CompressedBuffer		=	new char[CompressedBuffer_Len];
				int		Result					=	compress2((Bytef*)CompressedBuffer,&CompressedBuffer_Len,(Bytef*)Data,(uLong)DataLen,9);
				
				if ( Result == Z_OK )
				{				
					// Mark as encrypted, by saving the size of the uncompressed data.
					memcpy(_Compression->VariableData+_Compression->NameSize,&DataLen,sizeof(long));
					//delete[] Data;
					Data			= CompressedBuffer;
					DataLen			= CompressedBuffer_Len;
				}
				else
				{
					delete[] CompressedBuffer;
				}
			}
			else
			{
				// Not supported! Don't encrypt.
			}
		}
	}
#endif
	if ( TMP->Write(Data,DataLen,Path) == MAXSI_ERROR_SUCCESS )
	{
		TMP->Variables = Variables;
		TMP->Fragment = Fragment;
		Fragment = TMP;
	}
	else
	{
		delete TMP; // Failure, couldn't write this thing.
		TMP	= 0;
	}
#ifndef MAXSIEXTRACTOR	// The extractor shouldn't load this, since zlib1.dll may not exist!
	delete[] CompressedBuffer;
#endif
	return TMP;
}

// Exports the content of the file into an external buffer. Ensures the data isn't compressed or anything but pure.
int	MaxsiEngineFile::ExtractFile(char** Buffer, unsigned __int64* DataLen, MaxsiEngineFileFragment* TheFragment)
{
	int Result = 0;

	// First check if the file is compressed, if so, decompress it first!
#ifndef MAXSIEXTRACTOR	// The extractor shouldn't load this, since zlib1.dll may not exist!

	MEFFVariable*	_Compression			=	GetVariable(TheFragment->Variables,"_Compression");
	MEFFVariable*	_CompressionFormat		=	GetVariable(TheFragment->Variables,"_CompressionFormat");
	
	if ( _Compression && _CompressionFormat)
	{
		uLong	Decompressed_Size	= 0;
		memcpy(&Decompressed_Size,_Compression->VariableData+_Compression->NameSize,sizeof(Decompressed_Size));

		if (Decompressed_Size > 0) // Is this file compressed?
		{
			if (_stricmp(_CompressionFormat->VariableData+_CompressionFormat->NameSize,"zlib")==0)
			{
				char*	Decompressed_Buffer	=	new char[Decompressed_Size];
				Result						=	uncompress((Bytef*)Decompressed_Buffer,&Decompressed_Size,(Bytef*)TheFragment->Read(),TheFragment->GetFileSize()+1);
				
				if ( Result == Z_OK )
				{				
					Result = 1;
					*Buffer = Decompressed_Buffer;
					*DataLen = (unsigned __int64)Decompressed_Size;
				}
				else
				{
					Result -= 1024;
					delete[] Decompressed_Buffer;	
				}		
			}
			else
			{
				// Not supported! Don't decompress!!.
				Result = -2;
			}
		}
	}
	else
	{
#endif
		Result = 1;
		*Buffer = new char[TheFragment->GetFileSize()+1];
		memcpy(*Buffer,TheFragment->Read(),TheFragment->GetFileSize()+1);
		*DataLen = (unsigned __int64)TheFragment->GetFileSize()+1;
		if ( TheFragment->GetFileSize() == 0 )
		{
			*DataLen = 0;
		}
#ifndef MAXSIEXTRACTOR	// The extractor shouldn't load this, since zlib1.dll may not exist!
	}
#endif
	return Result;
}

// Writes the content of the fragment with the name equal to FileName to [Directory]/[FileName].[FileName Extension]
// or something like that. ;-)
int	MaxsiEngineFile::ExtractFile(MESTR* FileName, MESTR* Directory)
{
	return ExtractFile(ReadFile(FileName,0),Directory);
}

// Extracts all of the files in this MaxsiEngineFile class instance to the specified directory
int	MaxsiEngineFile::ExtractFiles(MESTR* Directory)
{
	for (MaxsiEngineFileFragment* TMP = Fragment; TMP; TMP = TMP->Fragment)
	{
		ExtractFile(TMP,Directory);
	}
	return 1;
}

// Extracts the fragment to the specified folder.
int	MaxsiEngineFile::ExtractFile(MaxsiEngineFileFragment* TheFragment, MESTR* Directory)
{
	if ( !TheFragment->GetFileName() || !Directory ) { return -1; }

	int				Result = 0;
	unsigned int	FilePathLen = 0;
	char*			FilePath = 0;

	// First generate the absolute filename
	if ( Directory[strlen(Directory)-1] == '/' || Directory[strlen(Directory)-1] == '\\' )
	{
		FilePathLen = (unsigned int)strlen(Directory) + strlen(TheFragment->GetFileName());
		FilePath = new char[FilePathLen+1];
		memcpy(FilePath,Directory,strlen(Directory));
		memcpy(FilePath+strlen(Directory),TheFragment->GetFileName(),strlen(TheFragment->GetFileName()));
		memset(FilePath+strlen(Directory)+strlen(TheFragment->GetFileName()),0,1);
	}
	else
	{
		FilePathLen = (unsigned int)strlen(Directory) + strlen(TheFragment->GetFileName());
		FilePath = new char[FilePathLen+2];
		memcpy(FilePath,Directory,strlen(Directory));
		memset(FilePath+strlen(Directory),'/',1);
		memcpy(FilePath+strlen(Directory)+1,TheFragment->GetFileName(),strlen(TheFragment->GetFileName()));
		memset(FilePath+strlen(Directory)+1+strlen(TheFragment->GetFileName()),0,1);
	}
	
	// Ensure the directory the file should be extracted to actually exists.
	size_t Len = strlen(FilePath);

	for (size_t I = 0; I < Len; I++)
	{
		if (FilePath[I] == '/' || FilePath[I] == '\\')
		{
			char* TempDir = new char[I+2];
			memcpy(TempDir,FilePath,I+1);
			memset(TempDir+I+1,0,1);
			_mkdir(TempDir);
			delete TempDir;
		}
	}		

	// Then open the file for write
	HANDLE	FileHandle	=	CreateFileA(FilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if (FileHandle  == INVALID_HANDLE_VALUE)
	{
		Result = -1;
	}
	else
	{
		// And write the data

		// First check if the file is compressed, if so, decompress it first!
#ifndef MAXSIEXTRACTOR	// The extractor shouldn't load this, since zlib1.dll may not exist!

		MEFFVariable*	_Compression			=	GetVariable(TheFragment->Variables,"_Compression");
		MEFFVariable*	_CompressionFormat		=	GetVariable(TheFragment->Variables,"_CompressionFormat");
		

		if ( _Compression && _CompressionFormat)
		{
			uLong	Decompressed_Size	= 0;
			memcpy(&Decompressed_Size,_Compression->VariableData+_Compression->NameSize,sizeof(Decompressed_Size));

			if (Decompressed_Size > 0) // Is this file compressed?
			{
				if (_stricmp(_CompressionFormat->VariableData+_CompressionFormat->NameSize,"zlib")==0)
				{
					char*	Decompressed_Buffer	=	new char[Decompressed_Size];
					Result						=	uncompress((Bytef*)Decompressed_Buffer,&Decompressed_Size,(Bytef*)TheFragment->Read(),TheFragment->GetFileSize()+1);
					
					DWORD	Written;

					if ( Result == Z_OK )
					{
						::WriteFile(FileHandle, Decompressed_Buffer, (DWORD)Decompressed_Size, &Written, NULL);
					}
					else
					{
						Result = -1;
					}
					delete[] Decompressed_Buffer;
					
				}
				else
				{
					// Not supported! Don't decompress!!.
					Result = -1;
				}
			}
		}
		else
		{
#endif
			if ( TheFragment->GetFileSize() )
			{
				DWORD	Written;
				::WriteFile(FileHandle, TheFragment->Read(), (DWORD)TheFragment->GetFileSize()+1, &Written, NULL);
			}
#ifndef MAXSIEXTRACTOR	// The extractor shouldn't load this, since zlib1.dll may not exist!
		}
#endif
		SetEndOfFile(FileHandle);
		CloseHandle(FileHandle);

		Result = 1;
	}

	return Result;
}

EndMaxsiNamespace

#endif

